require 'java'

class String
    # Transform a CamelCase String to a snake_case String.
    #--
    # Code has been taken from ActiveRecord
    def snake_case
        self.to_s.gsub(/::/, '/').
            gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
            gsub(/([a-z\d])([A-Z])/,'\1_\2').
            tr("-", "_").
            downcase
    end
end

module JMX
    class MBean
        include_package 'javax.management'
        include_package 'javax.management.remote'
        include_class 'java.util.HashMap'

        attr_reader :object_name, :operations, :attributes

        def initialize(object_name)
            @mbsc = MBean.establish_connection
            @object_name = object_name
            info = @mbsc.getMBeanInfo @object_name
            @attributes = Hash.new
            info.attributes.each do | mbean_attr |
                @attributes[mbean_attr.name.snake_case] = mbean_attr.name
                self.class.instance_eval do 
                    define_method mbean_attr.name.snake_case do
                        @mbsc.getAttribute @object_name, "#{mbean_attr.name}"
                    end
                end
                if mbean_attr.isWritable
                    self.class.instance_eval do
                        define_method "#{mbean_attr.name.snake_case}=" do |value| 
                            attr = Attribute.new mbean_attr.name, value
                            @mbsc.setAttribute @object_name, attr
                        end
                    end
                end
            end
            @operations = Hash.new
            info.operations.each do |mbean_op|
                param_types = mbean_op.signature.map {|param| param.type}
                @operations[mbean_op.name.snake_case] = [mbean_op.name, param_types]
            end
        end

        def method_missing(method, *args, &block)
            if @operations.keys.include?(method.to_s)
                op_name, param_types = @operations[method.to_s]
                @mbsc.invoke @object_name,
                    op_name,
                    args.to_java(:Object),
                    param_types.to_java(:String)
            else
                super
            end
        end

        def self.establish_connection(args={})
            host = args[:host] || "localhost"
            port = args[:port] || 3000
            @@mbsc ||= begin
                url = "service:jmx:rmi:///jndi/rmi://#{host}:#{port}/jmxrmi"
                connector = JMXConnectorFactory::connect JMXServiceURL.new(url), HashMap.new
                connector.getMBeanServerConnection
            end
        end

        def self.remove_connection
            @@mbsc = nil
        end

        def self.mbsc 
            establish_connection
        end

        def self.find_all_by_name(name)
            object_name = ObjectName.new(name)
            object_names = MBean.mbsc.queryNames(object_name, nil)
            object_names.map { |on| MBean.new(on) }
        end

        def self.find_by_name(name)
            MBean.new ObjectName.new(name)
        end
    end
end
